package com.cysoft.starter.log.core.helper;


import java.util.concurrent.TimeoutException;

/**
 * @author 玖零
 * @version v0.0.1
 * @project cysoft-spring-boot-starter-log
 * @package com.cysoft.starter.log.core.helper
 * @date 2025-02-26 23:49
 * @desc 雪花算法帮助类
 */
public class SnowflakeIdGeneratorHelper {

    // 起始时间戳 默认2025-02-25 00:00:00
    private static final long START_TIMESTAMP = 1740412800000L;
    // 机器ID所占的位数
    private static final long WORKER_ID_BITS = 5L;
    // 数据中心ID所占的位数
    private static final long DATA_CENTER_ID_BITS = 5L;
    // 序列所占的位数
    private static final long SEQUENCE_BITS = 12L;

    // 最大的机器ID，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    // 最大的数据中心ID，结果是31
    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);

    // 生成ID的时间截向左移的位数，就是后面的机器ID、数据中心ID、序列号的总位数
    private static final long TIMESTAMP_LEFT_SHIFT = WORKER_ID_BITS + DATA_CENTER_ID_BITS + SEQUENCE_BITS;
    // 机器ID向左移的位数，就是后面数据中心ID和序列号的总位数
    private static final long WORKER_ID_SHIFT = DATA_CENTER_ID_BITS + SEQUENCE_BITS;
    // 数据中心ID向左移的位数，就是序列号的位数
    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS;

    // 序列掩码，用于获取当前序列值，结果是4095 (111111111111)
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    // 工作机器ID
    private static long workerId;
    // 数据中心ID
    private static long dataCenterId;
    // 序列号
    private static long sequence = 0L;
    // 上次生成ID的时间戳
    private static long lastTimestamp = -1L;
    // 备用时钟
    private static long backupTimestamp = START_TIMESTAMP;
    // 是否正在使用备用时钟
    private static boolean usingBackupClock = false;
    // 最大等待时间，单位：毫秒
    private static final long MAX_WAIT_TIME = 5000;
    // 备用时钟重置间隔，单位：毫秒（每天重置一次）
    private static final long BACKUP_CLOCK_RESET_INTERVAL = 86400000;
    // 上次重置备用时钟的时间
    private static long lastBackupClockResetTime = System.currentTimeMillis();

    // 定义默认的 workerId 和 dataCenterId
    private static final long DEFAULT_WORKER_ID = 1;
    private static final long DEFAULT_DATA_CENTER_ID = 1;

    static {
        workerId = DEFAULT_WORKER_ID;
        dataCenterId = DEFAULT_DATA_CENTER_ID;
    }

    public static void init(long workerId, long dataCenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("Worker ID can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("Data center ID can't be greater than %d or less than 0", MAX_DATA_CENTER_ID));
        }
        SnowflakeIdGeneratorHelper.workerId = workerId;
        SnowflakeIdGeneratorHelper.dataCenterId = dataCenterId;
    }

    // 生成下一个ID
    public static synchronized long nextId() {
        long timestamp = timeGen();

        // 定期重置备用时钟
        if (System.currentTimeMillis() - lastBackupClockResetTime > BACKUP_CLOCK_RESET_INTERVAL) {
            backupTimestamp = START_TIMESTAMP;
            lastBackupClockResetTime = System.currentTimeMillis();
        }

        // 处理时钟回拨问题
        if (timestamp < lastTimestamp) {
            try {
                timestamp = handleClockBackward(lastTimestamp);
            } catch (TimeoutException e) {
                System.err.println("Clock did not recover within the maximum wait time: " + e.getMessage());
                throw new RuntimeException("Failed to generate ID due to clock backward issue", e);
            }
        }

        // 如果是同一时间生成的，则进行序列号自增
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // 如果序列号溢出，等待下一个毫秒的到来
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 不同时间生成的ID，序列号重置为0
            sequence = 0L;
        }

        // 记录上一次生成ID的时间戳
        lastTimestamp = timestamp;

        // 生成并返回ID
        return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) |
                (workerId << WORKER_ID_SHIFT) |
                (dataCenterId << DATA_CENTER_ID_SHIFT) |
                sequence;
    }

    // 处理时钟回拨
    private static long handleClockBackward(long lastTimestamp) throws TimeoutException {
        long startTime = System.currentTimeMillis();
        long timestamp = timeGen();
        while (timestamp < lastTimestamp) {
            if (System.currentTimeMillis() - startTime > MAX_WAIT_TIME) {
                // 超过最大等待时间，使用备用时钟
                if (!usingBackupClock) {
                    backupTimestamp = lastTimestamp;
                }
                usingBackupClock = true;
                timestamp = backupTimestamp + 1;
                backupTimestamp = timestamp;
                return timestamp;
            }
            try {
                // 等待一段时间后再次检查
                Thread.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            timestamp = timeGen();
        }
        long endTime = System.currentTimeMillis();
        long recoveryTime = endTime - startTime;
        if (recoveryTime > 0) {
            // 记录时钟回拨日志
            System.out.println("Clock moved backwards by " + (lastTimestamp - timestamp) + "ms and recovered in " + recoveryTime + "ms.");
        }
        if (usingBackupClock && timestamp > backupTimestamp) {
            // 时钟恢复正常，逐渐切换回使用系统时间戳
            usingBackupClock = false;
        }
        return timestamp;
    }

    // 阻塞到下一个毫秒，直到获得新的时间戳
    private static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    // 获取当前的时间戳
    private static long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        try {
            // 可以直接使用无参构造函数
            for (int i = 0; i < 10; i++) {
                long id = SnowflakeIdGeneratorHelper.nextId();
                System.out.println("Generated ID: " + id);
            }
        } catch (Exception e) {
            System.err.println("An error occurred while generating IDs: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
